1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.hiprenderer.backend.gl.gltexture; 12 13 version(OpenGL): 14 public import hip.api.renderer.texture; 15 public import hip.api.data.commons:IReloadable; 16 import hip.hiprenderer.config; 17 18 import hip.hiprenderer.backend.gl.glrenderer; 19 import hip.error.handler; 20 import hip.image; 21 import hip.math.utils; 22 23 class Hip_GL3_Texture : IHipTexture, IReloadable 24 { 25 GLuint textureID = 0; 26 int width, height; 27 uint currentSlot; 28 29 private IImage loadedImage; 30 IHipTexture getBackendHandle(){return this;} 31 bool hasSuccessfullyLoaded(){return width > 0;} 32 protected int getGLWrapMode(TextureWrapMode mode) 33 { 34 switch(mode) 35 { 36 case TextureWrapMode.CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; 37 case TextureWrapMode.REPEAT: return GL_REPEAT; 38 case TextureWrapMode.MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; 39 static if(!UseGLES) 40 { 41 //assert here would be better, as simply returning a default can be misleading. 42 case TextureWrapMode.MIRRORED_CLAMP_TO_EDGE: return GL_MIRROR_CLAMP_TO_EDGE; 43 case TextureWrapMode.CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; 44 } 45 default: return GL_REPEAT; 46 } 47 } 48 protected int getGLMinMagFilter(TextureFilter filter) 49 { 50 switch(filter) with(TextureFilter) 51 { 52 case LINEAR: 53 return GL_LINEAR; 54 case NEAREST: 55 return GL_NEAREST; 56 case NEAREST_MIPMAP_NEAREST: 57 return GL_NEAREST_MIPMAP_NEAREST; 58 case LINEAR_MIPMAP_NEAREST: 59 return GL_LINEAR_MIPMAP_NEAREST; 60 case NEAREST_MIPMAP_LINEAR: 61 return GL_NEAREST_MIPMAP_LINEAR; 62 case LINEAR_MIPMAP_LINEAR: 63 return GL_LINEAR_MIPMAP_LINEAR; 64 default: 65 return -1; 66 } 67 } 68 69 private __gshared int globalActiveSlot = 0; 70 ///256 textures should be enough 71 private __gshared Hip_GL3_Texture[256] boundTexture; 72 73 void bind(int slot = 0) 74 { 75 if(globalActiveSlot != slot) 76 { 77 glCall(() => glActiveTexture(GL_TEXTURE0+slot)); 78 globalActiveSlot = slot; 79 } 80 if(boundTexture[globalActiveSlot] !is this) 81 { 82 glCall(() => glBindTexture(GL_TEXTURE_2D, textureID)); 83 boundTexture[globalActiveSlot] = this; 84 } 85 currentSlot = slot; 86 } 87 88 void unbind(int slot = 0) 89 { 90 if(globalActiveSlot != slot) 91 { 92 glCall(() => glActiveTexture(GL_TEXTURE0+slot)); 93 globalActiveSlot = slot; 94 } 95 if(boundTexture[globalActiveSlot] is this) 96 { 97 glCall(() => glBindTexture(GL_TEXTURE_2D, 0)); 98 boundTexture[globalActiveSlot] = null; 99 } 100 currentSlot = slot; 101 } 102 103 void setWrapMode(TextureWrapMode mode) 104 { 105 int mod = getGLWrapMode(mode); 106 version(GLES2) 107 { 108 assert((isPowerOf2(width) && isPowerOf2(height)) || mod == TextureWrapMode.CLAMP_TO_EDGE, 109 "OpenGL ES 2.0/WebGL 1.0 must use Textures using Power of 2 size. If you wish to use "~ 110 "a non Power of 2, you must use the TextureWrapMode.CLAMP_TO_EDGE" 111 ); 112 } 113 bind(currentSlot); 114 glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mod)); 115 glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mod)); 116 } 117 118 void setTextureFilter(TextureFilter min, TextureFilter mag) 119 { 120 int min_filter = getGLMinMagFilter(min); 121 int mag_filter = getGLMinMagFilter(mag); 122 bind(currentSlot); 123 glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter)); 124 glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter)); 125 } 126 127 protected bool loadImpl(in IImage image) 128 { 129 loadedImage = cast(IImage)image; 130 glCall(() => glGenTextures(1, &textureID)); 131 if(textureID == 0) 132 { 133 ErrorHandler.assertExit(false, "No texture was generated for image ", image.getName); 134 } 135 int mode; 136 int internalFormat; 137 const(void)[] pixels = image.getPixels; 138 switch(image.getBytesPerPixel) 139 { 140 case 1: 141 if(image.hasPalette) 142 { 143 pixels = image.convertPalettizedToRGBA(); 144 version(GLES20) 145 { 146 internalFormat = mode = GL_RGBA; 147 } 148 else 149 { 150 mode = GL_RGBA; 151 internalFormat = GL_RGBA8; 152 } 153 } 154 else 155 { 156 version(GLES20) 157 { 158 internalFormat = mode = GL_LUMINANCE; 159 } 160 else 161 { 162 mode = GL_RED; 163 internalFormat = GL_R8; 164 } 165 } 166 break; 167 case 3: 168 version(GLES20) 169 { 170 internalFormat = mode = GL_RGB; 171 } 172 else 173 { 174 mode = GL_RGB; 175 internalFormat = GL_RGB8; 176 } 177 break; 178 case 4: 179 mode = GL_RGBA; 180 internalFormat = GL_RGBA; 181 break; 182 case 2: 183 default: 184 import hip.util.conv; 185 ErrorHandler.assertExit(false, "GL Pixel format unsupported on image "~image.getName~", bytesPerPixel: "~to!string(image.getBytesPerPixel)); 186 } 187 width = image.getWidth; 188 height = image.getHeight; 189 bind(currentSlot); 190 191 glCall(() => glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, image.getWidth, image.getHeight, 0, mode, GL_UNSIGNED_BYTE, cast(void*)pixels.ptr)); 192 glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); 193 glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); 194 setWrapMode(TextureWrapMode.REPEAT); 195 196 version(GLES20) 197 if(!isPowerOf2(image.getWidth) || !isPowerOf2(image.getHeight)) 198 { 199 setWrapMode(TextureWrapMode.CLAMP_TO_EDGE); 200 } 201 return true; 202 } 203 204 int getWidth() const {return width;} 205 int getHeight() const {return height;} 206 207 bool reload() 208 { 209 if(loadedImage !is null) 210 { 211 textureID = 0; 212 return loadImpl(loadedImage); 213 } 214 return false; 215 } 216 217 218 }